#ifndef __LIOPERATOR_INL__
#define __LIOPERATOR_INL__

#include "LIData.h"
#include "TypeDefine.h"
#include "sse_acceleration.inl"
#include <string.h>

namespace LARGE_INT {
namespace OPERA_UINT32 {

#define ATYP(size) LIntOpAdpter<UINT32,size>
#define const_ATYP(size) LIntOpAdpter<const UINT32,size>

	typedef void (*addsubFun)(const UINT32 *, const UINT32 *, UINT32 *, UINT32 &, UINT32 &);

	//************************************
	// add or sub using sse for 16 bytes unaligned LInt objects.
	//************************************
	template<int N,addsubFun fn>
	struct /*__declspec(align(16))*/ addsub_LInt_16bu_sse {
		
		addsub_LInt_16bu_sse(){
			clearCarry();
		}

		inline UINT32 run(ATYP(N) &lhs, const_ATYP(N) &rhs, ATYP(N) &ret)
		{
			doRun<MaxIter>(lhs,rhs,ret);
			UINT32 retFlag = m_carry0 | m_carry1;
			clearCarry();
			return retFlag;
		}

	private:
		static const int MaxIter = (N>>2)<<2;
		UINT32 m_carry0;
		UINT32 m_carry1;

	private:
		inline void clearCarry(){
			m_carry0 = 0;
			m_carry1 = 0;
		}

		template<int M>
		inline void doRun(ATYP(N) &lhs, const_ATYP(N) &rhs, ATYP(N) &ret)
		{
			fn(&lhs[MaxIter-M],&rhs[MaxIter-M],&ret[MaxIter-M],m_carry0,m_carry1);
			doRun<M-4>(lhs,rhs,ret);
		}

		template<>
		inline void doRun<4>(ATYP(N) &lhs, const_ATYP(N) &rhs, ATYP(N) &ret)
		{
			fn(&lhs[MaxIter-4],&rhs[MaxIter-4],&ret[MaxIter-4],m_carry0,m_carry1);
		}

		template<>
		inline void doRun<0>(ATYP(N) &lhs, const_ATYP(N) &rhs, ATYP(N) &ret)
		{
		}
	};

	template<int N>
	struct add_LInt_16bu_sse : public addsub_LInt_16bu_sse<N,SSE_OPS::add_4_uint32_sse_carry> {};

	template<int N>
	struct sub_LInt_16bu_sse : public addsub_LInt_16bu_sse<N,SSE_OPS::sub_4_uint32_sse_carry> {};


	//************************************
	// Condition: N>=4 && M>=4
	//************************************
	template<int M, int N>
	void opera_add_assign( ATYP(M) &lhs, const_ATYP(N) &rhs)
	{
		const int S = M>N?N:M;
		const int sse_size = (S>>2)<<2;

		ATYP(S) a(lhs);
		const_ATYP(S) b(rhs);
		add_LInt_16bu_sse<S> add_opera;
		UINT32 carry = add_opera.run(a,b,a);

		//TODO: optimize the following code.
		int L = sse_size;
		for (;L<S;++L)
		{
			UINT32 addend = lhs[L];
			lhs[L] += rhs[L];
			UINT32 local_carry = lhs[L]<addend?1:0;
			addend = lhs[L];
			lhs[L] += carry;
			local_carry |= lhs[L]<addend?1:0;
			carry = local_carry;
		}

		for (;L<M;++L)
		{
			UINT32 addend = lhs[L];
			lhs[L] += carry;
			carry = lhs[L]<addend?1:0;
		}
	}

	//************************************
	// Condition: N>=4 && M>=4
	//************************************
	template<int M, int N>
	void opera_sub_assign( ATYP(M) &lhs, const_ATYP(N) &rhs)
	{
		const int S = M>N?N:M;
		const int sse_size = (S>>2)<<2;

		ATYP(S) a(lhs);
		const_ATYP(S) b(rhs);
		sub_LInt_16bu_sse<S> sub_opera;
		UINT32 carry = sub_opera.run(a,b,a);

		//TODO: optimize the following code.
		int L = sse_size;
		for (;L<S;++L)
		{
			UINT32 minuend = lhs[L];
			lhs[L] -= rhs[L];
			UINT32 local_carry = minuend<rhs[L]?1:0;
			minuend = lhs[L];
			lhs[L] -= carry;
			local_carry |= minuend<carry?1:0;
			carry = local_carry;
		}

		for (;L<M;++L)
		{
			UINT32 minuend = lhs[L];
			lhs[L] -= carry;
			carry = minuend<carry?1:0;
		}

	}

	struct mul_LInt_16bu_sse {
		template<int N>
		static inline void doRun(const_ATYP(N) &lhs, const_ATYP(N) &rhs, ATYP(2*N) &ret )
		{
			const int H = N>>1;
			const int L = N - H;

			const_ATYP(H) upper_half_a(lhs,L), upper_half_b(rhs,L);
			const_ATYP(L) lower_half_a(lhs), lower_half_b(rhs);

			/*allocate some buffer for storing intermediate results.*/
			LARGE_INT::LargeInt<UINT32,L+1> sum_hl_buffer, sum_hr_buffer;
			LARGE_INT::LargeInt<UINT32,2*(L+1)> third_product_buffer;

			/*calculate the first two products.*/
			ATYP(2*H) product_high(ret,2*L);
			ATYP(2*L) product_low(ret);

			doRun<H>(upper_half_a,upper_half_b,product_high);
			doRun<L>(lower_half_a,lower_half_b,product_low);

			/*calculate the sum of each half of each inputs respectively.*/
			ATYP(L+1) sum_a(sum_hl_buffer), sum_b(sum_hr_buffer);
			
			//init sum_a and sum_b with the value of each lower half from the inputs.
			sum_a.copyMem(lower_half_a);
			sum_b.copyMem(lower_half_b);

			opera_add_assign(sum_a,upper_half_a);
			opera_add_assign(sum_b,upper_half_b);

			/*calculate the third product.*/
			ATYP(2*(L+1)) product_middle(third_product_buffer);

			doRun<L+1>((const_ATYP(L+1))sum_a,(const_ATYP(L+1))sum_b,product_middle);

			/*subtract the first two products.*/
			opera_sub_assign(product_middle,(const_ATYP(2*L))product_low);
			opera_sub_assign(product_middle,(const_ATYP(2*H))product_high);

			/*sum up the final result in order.*/
			ATYP(2*N-L) accum_middle(ret,L);
			opera_add_assign(accum_middle,(const_ATYP(2*(L+1)))product_middle);
		}

		template<>
		static inline void doRun<3>(const_ATYP(3) &lhs, const_ATYP(3) &rhs, ATYP(6) &ret)
		{
			LARGE_INT::LargeInt<UINT32,2> exd_upper_a_buffer, exd_upper_b_buffer;
			ATYP(1) upper_half_a(exd_upper_a_buffer), upper_half_b(exd_upper_b_buffer);
			upper_half_a.copyMem(lhs,2);
			upper_half_b.copyMem(rhs,2);
			
			const_ATYP(2) lower_half_a(lhs), lower_half_b(rhs);

			LARGE_INT::LargeInt<UINT32,4> product_cross_buffer0, product_cross_buffer1;

			/*first two products.*/
			ATYP(2) product_high(ret,4);
			doRun<1>((const_ATYP(1))upper_half_a,(const_ATYP(1))upper_half_b,product_high);
			ATYP(4) product_low(ret);
			doRun<2>(lower_half_a,lower_half_b,product_low);

			/*two cross products.*/
			const_ATYP(2) exd_upper_a(upper_half_a), exd_upper_b(upper_half_b);
			ATYP(4) product_cross0(product_cross_buffer0), product_cross1(product_cross_buffer1);
			doRun<2>(exd_upper_a,lower_half_b,product_cross0);
			doRun<2>(exd_upper_b,lower_half_a,product_cross1);

			/*sum up results.*/
			ATYP(4) accum_middle(ret,2);
			opera_add_assign(accum_middle,(const_ATYP(4))product_cross0);
			opera_add_assign(accum_middle,(const_ATYP(4))product_cross1);
		}

		template<>
		static inline void doRun<2>(const_ATYP(2) &lhs, const_ATYP(2) &rhs, ATYP(4) &ret)
		{
			SSE_OPS::mul_64_to_128_sse(lhs[1],lhs[0],rhs[1],rhs[0],ret.get());
		}

		template<>
		static inline void doRun<1>(const_ATYP(1) &lhs, const_ATYP(1) &rhs, ATYP(2) &ret)
		{
			UINT64 a = lhs[0];
			UINT64 b = rhs[0];
			UINT64 r = a * b;
			memcpy(ret.get(),&r,sizeof(UINT64));
		}
	};


	template<int N>
	void opera_mul( const_ATYP(N) &lhs, const_ATYP(N) &rhs, ATYP(2*N) &ret )
	{
		mul_LInt_16bu_sse::doRun<N>(lhs,rhs,ret);
	}


#undef const_ATYP
#undef ATYP
}}


#endif